
var  dateTimeFormats =
{
  millisecond:"%b %e, %H:%M:%S.%L",
  second:"%b %e, %H:%M:%S",
  minute:"%b %e, %H:%M",
  hour:"%b %e, %H:%M",
  day:"%b %e, %Y",
  week:"Week from %b %e, %Y",
  month:"%B %Y",
  year:"%Y"
};

var  dataGroupingTimeFormats =
{
  millisecond: ['%b %e, %H:%M:%S.%L', '%b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'],
  second: ['%b %e, %H:%M:%S', '%b %e, %H:%M:%S', '-%H:%M:%S'],
  minute: ['%b %e, %H:%M', '%b %e, %H:%M', '-%H:%M'],
  hour: ['%b %e, %H:%M', '%b %e, %H:%M', '-%H:%M'],
  day: ['%b %e, %Y', '%b %e', '-%b %e, %Y'],
  week: ['Week from %b %e, %Y', '%b %e', '-%b %e, %Y'],
  month: ['%B %Y', '%B', '-%B %Y'],
  year: ['%Y', '%Y', '-%Y']
};


function mergeRecursive(src, dst) {
    for (var p in dst) {
        try {
            // Property in destination object set; update its value.
            if (dst[p].constructor == Object) {
                src[p] = mergeRecursive(src[p], dst[p]);

            } else {
                src[p] = dst[p];

            }

        } catch (e) {
            // Property in destination object not set; create it and set its value.
            src[p] = dst[p];

        }
    }

    return src;
}


function mergeOptions( parameterObject,defaultOptions ) {
  defaultOptions = defaultOptions || {};
	var mergedOptions = $.extend( {}, defaultOptions );
	if ( parameterObject ) {
		var trimmedParameters = {};
		for ( var key in parameterObject ) {
			trimmedParameters[ key.trim() ] = parameterObject[ key ];
		}
		
		$.extend( mergedOptions, trimmedParameters )
	}

	return mergedOptions;
}

function isDefined(value) {
    return typeof value != "undefined";
}

function isUndefined(value) {
    return !isDefined(value);
}

function isNotValued(value) {
    return !isValued(value);
}

function isValued(value) {
    return isDefined(value) && value != null;
}

function getParameterByName(name) {
    name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
    var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
      results = regex.exec(location.search);
    return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
  }
  
  function getBoolean( value ) {
      if ( value == null ) return false;
      if ( typeof value == "boolean" ) return value;
    var lowerCase = value.toLowerCase().trim();
  
      return lowerCase == "true" || lowerCase == "y" || lowerCase == "yes";
  }
  
  function getDefaultFontSize() {
    return "10px";
  }
  
  function getDefaultFontFamily() {
    return "Tahoma, sans-serif";
  }
  
  function newUUID (number) {
    var num =  number || 16;
    var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
        var r = Math.random() * 16 | 0,
            v = c === 'x' ? r : (r & 0x3 | 0x8);
        return v.toString(num);
    });
    return uuid;
}

function hashDate ( dateObject ) {
  return dateObject.getHours() * 60 * 60 * 1000 + dateObject.getMinutes() * 60 * 1000 + dateObject.getSeconds() * 1000 + dateObject.getMilliseconds();
}

function isWithinBusinessHours(millisecondTime,businessHours){
  var dateObject = new Date( millisecondTime );
  var index;
  var range;
  var afterStart;
  var beforeEnd;
  if ( !businessHours || businessHours.length == 0 ) return true;
  var dateHash = hashDate( dateObject );


  for ( index = 0; index < businessHours.length; index++ ) {
    range = businessHours[ index ];


    afterStart = hashDate( range.start ) <= dateHash;
    //no need to check the end if we are not in range
    if ( !afterStart ) continue;

    beforeEnd = hashDate( range.end ) >= dateHash;

    if ( afterStart && beforeEnd ) return true;
  }
  return false;
}

Array.prototype.extend = function (other_array) {
  /* you should include a test to check whether other_array really is an array */
  other_array.forEach(function (v) {
    this.push(v)
  }, this);
}


/**
 * @private
 */
var _MINUTE_OFFSET = 1000 * 60;
/**
 * @private
 */
var _HOUR_OFFSET = _MINUTE_OFFSET * 60;
/**
 * @private
 */
var _DAY_OFFSET = _HOUR_OFFSET * 24;
/**
 * @private
 */
var _WEEK_OFFSET = _DAY_OFFSET * 7;
/**
 * @private
 */
var _MONTH_OFFSET = _DAY_OFFSET * 30;
/**
 * @private
 */
var _YEAR_OFFSET = _DAY_OFFSET * 365;

/**
 * For live windows, give absolute offset time
 * @param count
 * @param type
 * @returns {*}
 */
function getTimeOffset( count, type,targetTimeStamp ) {
	var baseOffset = 0;
	switch ( type ) {
		case "all" : return null;
		case "minute" : baseOffset = _MINUTE_OFFSET; break;
		case "hour" : baseOffset = _HOUR_OFFSET; break;
		case "day" : baseOffset = _DAY_OFFSET; break;
		case "week" : baseOffset = _WEEK_OFFSET; break;
		case "month" : baseOffset = _MONTH_OFFSET; break;
    case "year" : baseOffset = _YEAR_OFFSET; break;
    case "ytd"  : 
      var target = new Date(targetTimeStamp);
      target.setMilliseconds(0);
      target.setSeconds(0);
      target.setMinutes(0);
      target.setDate(1);
      target.setMonth(0);
      target.setUTCHours(0);
      return Math.abs( targetTimeStamp-target.getTime() );
	}

	return targetTimeStamp - baseOffset * count;
}

function DateUtils() {

	this.MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
  this.MILLIS_PER_MONTH = this.MILLIS_PER_DAY * 30;
  this.MILLIS_PER_YEAR = this.MILLIS_PER_DAY * 365;
  this.FORMAT_MONTH_DAY_YEAR = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric"  });
}

var _dateUtils = new DateUtils();
var DATE_UTILS = _dateUtils;

DateUtils.prototype.getMidnight = function( millis ) {
	var date = new Date( millis );
	date.setHours( 0 );
	date.setMinutes( 0 );
	date.setSeconds( 0 );
	date.setMilliseconds( 0 );

	return date.getTime();
}

DateUtils.prototype.setMidnight = function( date ) {
  date.setHours( 0 );
  date.setMinutes( 0 );
  date.setSeconds( 0 );
  date.setMilliseconds( 0 );

  return date;
}

DateUtils.prototype.setFirstOfMonth = function( date ) {
  this.setMidnight( date );
  date.setDate( 1 );

  return date;
}

DateUtils.prototype.setFirstOfYear = function( date ) {
  this.setFirstOfMonth( date );
  date.setMonth( 0 );

  return date;
}

DateUtils.prototype.addDay = function( date ) {
  date.setDate( date.getDate() + 1 );
  return date;
}

DateUtils.prototype.addMonth = function( date ) {
  date.setMonth( date.getMonth() + 1 );
  return date;
}

DateUtils.prototype.addYear = function( date ) {
  date.setYear( date.getFullYear() + 1 );
  return date;
}




/**
 * Number utilities
 * @constructor
 */
function NumberUtils() {

}
var NUMBER_UTILS = new NumberUtils();
var _DEFAULT_SIGNIFICANT = 4;

NumberUtils.prototype.createFormatter = function ( significantDigits ) {
  return new Intl.NumberFormat("en-US", {
    minumumFractionDigits: 0 ,
    maximumFractionDigits: significantDigits,
    maximumSignificantDigits: significantDigits
  });
}

NumberUtils.prototype.randomInterval = function ( min, max ) {
	return Math.floor(Math.random() * (max - min + 1) + min);
};

var _formatterMap = {};
_formatterMap[_DEFAULT_SIGNIFICANT] = NUMBER_UTILS.createFormatter( _DEFAULT_SIGNIFICANT );

NumberUtils.prototype.formatSignificant = function( value, numDigits ) {
  var numValue = value;
  if ( typeof value == "string") {
    numValue = parseFloat( value.trim() );
    if ( isNaN( numValue ) ) return value;
  }
  var formatter = _formatterMap[ numDigits ];
  if ( !isDefined( formatter ) ) {
    formatter = this.createFormatter( numDigits );
    _formatterMap[ numDigits ] = formatter;
  }

  return formatter.format( numValue );
}


function StringUtils() {

	this.isEqual = function( string1, string2 ) {
		if ( isNotValued( string1 ) && isNotValued( string2 ) ) return true;
		if ( isNotValued( string1 ) || isNotValued( string2 ) ) return false;
		return string1.trim().toLowerCase() == string2.trim().toLowerCase();
	}

	this.isNotEqual = function( string1, string2 ) {		
		return !this.isEqual( string1, string2 );
	}

	this.toLowerCase = function( string1 ) {
		if ( isNotValued( string1 ) ) return string1;
		return string1.toLowerCase();
	}

	this.leftPad = function( string1, character, amount ) {
		var returnValue = string1;
		while ( returnValue.length < amount ) {
			returnValue = character + returnValue;
		}
		return returnValue;
	}	
}



var STRING_UTILS = new StringUtils();

function ArrayUtils() {
  this.shuffle = function (array) {
	if (!isValued(array)) {
		return null;
	}
	var currentIndex = array.length, temporaryValue, randomIndex;

	// While there remain elements to shuffle...
	while (0 !== currentIndex) {

		// Pick a remaining element...
		randomIndex = Math.floor(Math.random() * currentIndex);
		currentIndex -= 1;

		// And swap it with the current element.
		temporaryValue = array[currentIndex];
		array[currentIndex] = array[randomIndex];
		array[randomIndex] = temporaryValue;
	}

	return array;
	}
	
	this.printArray = function( array ) {
		for (x in array) {
			if (array[x] instanceof Array) {
				array[x] = "[" + array[x].join(",") + "]";
			}
		}
		return "[" + array.join(",") + "]";
	}
}

var ARRAY_UTILS = new ArrayUtils();

function getOption(options, property, defaultValue) {
  return typeof options !== 'undefined' && options != null && options.hasOwnProperty(property) ? options[property] : defaultValue;
}

function assignYAxis(label, options,highCharts) {
  var yaxisId = getYAxisId(label);
  var yaxis = highCharts.get(yaxisId);

  if (!yaxis && label) {
    options.left =( highCharts.yAxis.length ) % 2 == 0;
    var axisOptions = createAxisOptions(label, options);
    highCharts.addAxis(axisOptions, false, false, false);
    // if (axisOptions.opposite) {
    //   highCharts.xAxis[0].width = highCharts.xAxis[0].width - 30;
    // }
    // redistributePanes();
  }
  return yaxisId;
}

function getYAxisId(seriesName) {
  return "y" + seriesName;
}
var AXIS_SIGNIFICANT_DIGITS = 4;
function formatYAxis(  ) {
  var currentAxis = this.axis;
  var significantDigits = isDefined( currentAxis["significantDigits"] ) ? currentAxis["significantDigits"] : AXIS_SIGNIFICANT_DIGITS;

  if ( this.isFirst ) {
    significantDigits = this.axis.getSignificantDigits();
    currentAxis[ 'significantDigits' ] = significantDigits;
  }

  if ( this.isLast ) {
    delete currentAxis[ 'significantDigits' ];
  }

  return NUMBER_UTILS.formatSignificant( this.value, significantDigits );
}

function createAxisOptions ( axisName, options ) {
  var axisId = getYAxisId( axisName );
  var paneIndex = getOption( options, "paneIndex", 0);  
  var axisType = getOption( options, 'axisType', 'linear' );
  var x = getOption( options, 'x', 18 );
  var offset = getOption( options, 'offset', 0 );
  var lineColor = getOption( options, 'lineColor', "#ccd6eb" );
  var options = {
    id: axisId,
    lineWidth: 1,
    type: axisType,
    crosshair: false,
    opposite: true,
    title: {
        //text: axisName
        text: ' ',
    },
    labels: {
        align: "left",
        x: x,
        y: 0,
        useHTML: false,
        formatter: formatYAxis,
        style: {
            color: "#FFFFFF",
            fontSize: "inherit"
        }

    },
    tickLength: 5,
    tickWidth: 1,
    gridLineDashStyle: "dot",
    gridLineWidth: 0,
    paneIndex: paneIndex,
    gridLineColor: "#618661",
    gridLineDashStyle: "dot",
    gridLineWidth: 1,
    lineColor: lineColor,
    startOnTick: false,
    endOnTick: false,
    offset:offset,
    tickPosition: 'outside',
    showLastLabel: true,
    currentPriceIndicator: createLastPriceOptions()
}
    
  if ( paneIndex == 1) {
    options.height = "20%";
    options.top = "80%";      
  }   

  if ( lineColor != null ) {
    options.lineColor = lineColor;
  }


  return options;
}

function createLastPriceOptions() {
  
  return {
    lineDashStyle: 'Solid',
    zIndex: 2,
    lineOpacity: 1.0,
    lineColor: "#FFFF00",
    style : {
      fontSize: "10px"
    }
  };
}